package cn.tonyqiu.gaecash.model;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.NotPersistent;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

import cn.tonyqiu.gaecash.PMF;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;
import com.google.appengine.api.users.User;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Bill extends BaseModel {

	@PrimaryKey
	@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
	private Key key;
	@Persistent
	private Date date;
	@Persistent
	private Double amount;
	@Persistent
	private Set<String> labelSet;
	@Persistent
	private Text remark;
	@Persistent
	private User user;
	@Persistent
	private Date createDate;
	@Persistent
	private Date modifiedDate;
	@NotPersistent
	private String keyString;
	@NotPersistent
	private List<Label> labels;

	public Bill() {
		this.createDate = new Date();
	}

	public Bill(Date date, Double amount, List<Label> labels, String remark,
			User user) {
		this.date = date;
		this.amount = amount;
		this.labels = labels;
		this.remark = new Text(remark);
		this.user = user;
		createDate = new Date();
	}

	public Date getCreateDate() {
		return createDate;
	}

	public void setCreateDate(Date createDate) {
		this.createDate = createDate;
	}

	public Date getModifiedDate() {
		return modifiedDate;
	}

	public void setModifiedDate(Date modifiedDate) {
		this.modifiedDate = modifiedDate;
	}

	public User getUser() {
		return user;
	}

	public void setUser(User user) {
		this.user = user;
	}

	public Text getRemark() {
		return remark;
	}

	public void setRemark(Text remark) {
		this.remark = remark;
	}

	public void setRemark(String remark) {
		this.remark = new Text(remark);
	}

	public Key getKey() {
		return key;
	}

	public void setKey(Key key) {
		this.key = key;
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}

	public Double getAmount() {
		return amount;
	}

	public void setAmount(Double amount) {
		this.amount = amount;
	}

	public Set<String> getLabelSet() {
		if (labelSet == null)
			labelSet = new HashSet<String>();
		return labelSet;
	}

	public void setLabelSet(Set<String> labels) {
		this.labelSet = labels;
	}

	public void addLable(String labelKey) {
		if (labelSet == null)
			labelSet = new HashSet<String>();
		labelSet.add(labelKey);
	}

	public void removeLabel(String labelKey) {
		if (labelSet != null) {
			labelSet.remove(KeyFactory.stringToKey(labelKey));
		}
	}

	public void removeLabel(Key labelKey) {
		if (labelSet != null) {
			labelSet.remove(labelKey);
		}
	}

	@SuppressWarnings("unchecked")
	public static Map<String, Object> getByPage(User user, int start, int count) {
		Map<String, Object> map = new HashMap<String, Object>();
		PersistenceManager pm = PMF.get().getPersistenceManager();
		List<Bill> bills = null;
		Query query = pm.newQuery(Bill.class);
		query.setOrdering("date desc");
		query.setRange(start, start + count);
		query.setFilter("user == paramUser");
		query.declareParameters("java.lang.String paramUser");
		bills = (List<Bill>) query.execute(user);
		// getLabels
		for (Bill bill : bills) {
			for (String key : bill.getLabelSet()) {
				if (key != null && !"".equals(key)) {
					bill.getLabels().add(pm.getObjectById(Label.class, key));
				}
			}
		}
		map.put("bills", bills);
		map.put("billInfo", getBillInfo(user, start+1, start+count));
		return map;
	}
	@SuppressWarnings("unchecked")
	public static List<Bill> filterByLabel(User user, int start, int count,
			Set<String> label) {
		// TODO filterByLabel
		return null;
	}
	@SuppressWarnings("unchecked")
	public static Map<String, Object> filterByDate(User user, int start, int count,
			Date from, Date to) {
		Map<String, Object> map = new HashMap<String, Object>();
		PersistenceManager pm = PMF.get().getPersistenceManager();
		List<Bill> bills = null;
		if (from != null && to != null) {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setOrdering("date desc");
			query.setRange(start, start + count);
			query.setFilter("user == paramUser && date >= from && date <= to");
			query.declareParameters("java.lang.String paramUser, java.util.Date from, java.util.Date to");
			bills = (List<Bill>) query.execute(user,from, to);
		} else if (from != null) {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setOrdering("date desc");
			query.setRange(start, start + count);
			query.setFilter("user == paramUser && date >= from");
			query.declareParameters("java.lang.String paramUser, java.util.Date from");
			bills = (List<Bill>) query.execute(user,from);
		} else if (to != null) {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setOrdering("date desc");
			query.setRange(start, start + count);
			query.setFilter("user == paramUser && date <= to");
			query.declareParameters("java.lang.String paramUser, java.util.Date to");
			bills = (List<Bill>) query.execute(user,to);
		} else {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setOrdering("date desc");
			query.setRange(start, start + count);
			query.setFilter("user == paramUser");
			query.declareParameters("java.lang.String paramUser");
			bills = (List<Bill>) query.execute(user);
			
		}
		// getLabels
		for (Bill bill : bills) {
			for (String key : bill.getLabelSet()) {
				if (key != null && !"".equals(key)) {
					bill.getLabels().add(pm.getObjectById(Label.class, key));
				}
			}
		}
		map.put("bills", bills);
		map.put("billInfo", getFilteredBillInfo(user,from,to, start+1, start+count));
		return map;
	}

	@SuppressWarnings("unchecked")
	public Bill add() throws Exception {
		pm = PMF.get().getPersistenceManager();
		if (createDate == null) {
			createDate = new Date();
		}
		// Create a unique key for this bill
		String billKey = KeyFactory.keyToString(KeyFactory
				.createKey(Bill.class.getSimpleName(), date.toString() + ","
						+ createDate.toString()));

		for (Label label : labels) {
			pm.currentTransaction().begin();
			try {
				Query query = pm.newQuery("select from "
						+ Label.class.getName());
				query.setFilter("user == paramUser && name == paramName");
				query
						.declareParameters("java.lang.String paramUser,java.lang.String paramName");
				List<Label> queryLabels = (List<Label>) query.execute(label
						.getUser(), label.getName());
				// if Label exist
				if (queryLabels.size() >= 1) {
					label = queryLabels.get(0);
					this.addLable(label.getKeyString());
					label.addBill(billKey);
				} else {// if Label does't exist
					label.addBill(billKey);
					label = pm.makePersistent(label);
					this.addLable(label.getKeyString());
				}
				pm.currentTransaction().commit();
			} finally {
				if (pm.currentTransaction().isActive()) {
					pm.currentTransaction().rollback();
				}
			}
		}
		pm.close();
		this.setKey(KeyFactory.stringToKey(billKey));
		pm = PMF.get().getPersistenceManager();
		pm.makePersistent(this);

		return this;
	}

	@SuppressWarnings("unchecked")
	public void edit() throws Exception {
		pm = PMF.get().getPersistenceManager();
		// First get the replacement label set
		Set<String> replacementLabelSet = new HashSet<String>();
		Set<String> originalLabelSet = null;
		Set<String> toDeleteLabelSet = null;
		for (Label label : getLabels()) {
			pm.currentTransaction().begin();
			try {
				Query query = pm.newQuery("select from "
						+ Label.class.getName());
				query.setFilter("user == paramUser && name == paramName");
				query
						.declareParameters("java.lang.String paramUser,java.lang.String paramName");
				List<Label> queryLabels = (List<Label>) query.execute(label
						.getUser(), label.getName());
				// if Label exist
				if (queryLabels.size() >= 1) {
					label = queryLabels.get(0);
					label.addBill(this.getKeyString());
					replacementLabelSet.add(label.getKeyString());
				} else {// if Label does't exist
					label.addBill(this.getKeyString());
					label = pm.makePersistent(label);
					replacementLabelSet.add(label.getKeyString());
				}
				pm.currentTransaction().commit();
			} finally {
				if (pm.currentTransaction().isActive()) {
					pm.currentTransaction().rollback();
				}
			}
		}
		pm.close();

		// update bill
		pm = PMF.get().getPersistenceManager();
		Bill originalBill = pm.getObjectById(Bill.class, getKeyString());
		originalLabelSet = originalBill.getLabelSet();
		originalBill.setModifiedDate(new Date());
		originalBill.setAmount(getAmount());
		originalBill.setDate(getDate());
		originalBill.setLabelSet(replacementLabelSet);
		originalBill.setRemark(getRemark());
		pm.close();

		// delete the bill from labels
		originalLabelSet.removeAll(replacementLabelSet);
		toDeleteLabelSet = originalLabelSet;
		if (toDeleteLabelSet.size() > 0) {
			pm = PMF.get().getPersistenceManager();
			pm.currentTransaction().begin();
			try {
				for (String labelKeyId : toDeleteLabelSet) {
					Label label = pm.getObjectById(Label.class, labelKeyId);
					label.removeBill(getKeyString());
				}
				pm.currentTransaction().commit();
			} finally {
				if (pm.currentTransaction().isActive())
					pm.currentTransaction().rollback();
				pm.close();
			}
		}
	}

	public static void del(String keyString) {
		Set<String> labelSet = null;
		String billKey = null;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		pm.currentTransaction().begin();
		try {
			Bill bill = pm.getObjectById(Bill.class, keyString);
			billKey = bill.getKeyString();
			labelSet = bill.getLabelSet();
			pm.deletePersistent(bill);
			pm.currentTransaction().commit();
		} finally {
			if (pm.currentTransaction().isActive())
				pm.currentTransaction().rollback();
			pm.close();
		}

		pm = PMF.get().getPersistenceManager();
		pm.currentTransaction().begin();
		Label label = null;
		try {
			for (String labelKey : labelSet) {
				label = pm.getObjectById(Label.class, labelKey);
				label.removeBill(billKey);
			}
			pm.currentTransaction().commit();
		} finally {
			if (pm.currentTransaction().isActive())
				pm.currentTransaction().rollback();
			pm.close();
		}
	}

	public String getKeyString() {
		if (key != null)
			keyString = KeyFactory.keyToString(key);
		return keyString;
	}

	public void setKeyString(String keyString) {
		this.keyString = keyString;
	}

	public List<Label> getLabels() {
		if (labels == null)
			labels = new ArrayList<Label>();
		return labels;
	}

	public void setLabels(List<Label> labels) {
		this.labels = labels;
	}

	@SuppressWarnings("unchecked")
	private static BillInfo getBillInfo(User user, Integer currentDateIndex,
			Integer currentLastDateIndex) {
		BillInfo info = new BillInfo();

		info.setCountPerPage(currentLastDateIndex - currentDateIndex + 1);
		info.setCurrentDateIndex(currentDateIndex);

		PersistenceManager pm = PMF.get().getPersistenceManager();
		Query query = pm.newQuery("select from " + Bill.class.getName());
		query.setFilter("user == paramUser");
		query.declareParameters("java.lang.String paramUser");
		List<Bill> bills = (List<Bill>) query.execute(user);
		info.setDateCount(bills.size());
		if (currentLastDateIndex > bills.size()) {
			info.setCurrentLastDateIndex(bills.size());
		} else {
			info.setCurrentLastDateIndex(currentLastDateIndex);
		}
		Double totalIncome = 0d;
		Double totalOutcome = 0d;
		for (Bill bill : bills) {
			if (bill.getAmount() >= 0) {
				totalIncome += bill.getAmount();
			} else {
				totalOutcome += bill.getAmount();
			}
		}
		info.setTotalIncome(totalIncome);
		info.setTotalOutcome(totalOutcome);
		info.setBalance(totalIncome + totalOutcome);
		pm.close();
		return info;
	}
	
	@SuppressWarnings("unchecked")
	private static BillInfo getFilteredBillInfo(User user,
			Date from, Date to, Integer currentDateIndex,
			Integer currentLastDateIndex) {
		PersistenceManager pm = PMF.get().getPersistenceManager();
		List<Bill> bills = null;
		if (from != null && to != null) {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setFilter("user == paramUser && date >= from && date <= to");
			query.declareParameters("java.lang.String paramUser, java.util.Date from, java.util.Date to");
			bills = (List<Bill>) query.execute(user,from, to);
		} else if (from != null) {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setFilter("user == paramUser && date >= from");
			query.declareParameters("java.lang.String paramUser, java.util.Date from");
			bills = (List<Bill>) query.execute(user,from);
		} else if (to != null) {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setFilter("user == paramUser && date <= to");
			query.declareParameters("java.lang.String paramUser, java.util.Date to");
			bills = (List<Bill>) query.execute(user,to);
		} else {
			Query query = pm.newQuery("select from " + Bill.class.getName());
			query.setFilter("user == paramUser");
			query.declareParameters("java.lang.String paramUser");
			bills = (List<Bill>) query.execute(user);
			
		}
		BillInfo info = new BillInfo();
		info.setCountPerPage(currentLastDateIndex - currentDateIndex + 1);
		info.setCurrentDateIndex(currentDateIndex);
		info.setDateCount(bills.size());
		if (currentLastDateIndex > bills.size()) {
			info.setCurrentLastDateIndex(bills.size());
		} else {
			info.setCurrentLastDateIndex(currentLastDateIndex);
		}
		Double totalIncome = 0d;
		Double totalOutcome = 0d;
		for (Bill bill : bills) {
			if (bill.getAmount() >= 0) {
				totalIncome += bill.getAmount();
			} else {
				totalOutcome += bill.getAmount();
			}
		}
		info.setTotalIncome(totalIncome);
		info.setTotalOutcome(totalOutcome);
		info.setBalance(totalIncome + totalOutcome);
		return info;
	}

}
